Next | Prev | Up | Top | Contents | Index

GIO Devices with Hardware-supported Scatter/Gather Capability

Chapter 2, "Writing a Device Driver," tells you to use the physio() kernel routine to fault in and lock the physical pages corresponding to the user's buffer. physio() also remaps these physical pages to a kernel virtual address that remains constant even when the user's virtual addresses are no longer mapped.

Internally, physio() allocates a structure of type buf if you pass a NULL pointer. (physio() uses this structure to embody the transfer information.) physio() then calls your drvstrategy() routine and passes it a pointer to the buf type structure that it has allocated and primed. Your drvstrategy() routine must then loop through each page, starting at the kernel virtual address, and load each device scatter/gather register in turn with the corresponding physical address. Use the kvtophys() routine to convert a kernel virtual address to a physical address.

For example, suppose the mythical device is now a GIO device that has hardware-supporting scatter/gather. The scatter/gather registers for the device are simply a table of integers that store the physical pages corresponding to the current transfer. To start the transfer, the driver gives the device the beginning byte offset, byte count, and transfer direction. The code is:

/* Actual device setup for DMA, etc., if your board has
 * hardware scatter/gather DMA support.
 * Called from the gbdwrite() routine via physio().
 */
void
gbd_strategy(struct buf *bp)
{
   int unit = geteminor(bp->b_dev)&1;
   int npages;
   volatile unsigned *sgregisters;
   int i, v_addr;

   /* Get address of the scatter/gather registers */
   sgregisters = gbd_device[unit]->sgregisters;

   /* Get the kernel virtual address of the data; note
   * b_dmaaddr may be NULL if the BP_ISMAPPED(bp) macro
   * indicates false; in that case, the field bp->b_pages
   * is a pointer to a linked list of pfdat structure
   * pointers; that saves creating a virtual mapping and
   * then decoding that mapping back to physical addresses.
   * BP_ISMAPPED will never be false for character devices,
   * only block devices.
   */
   if(!BP_ISMAPPED(bp)) {
      cmn_err(CE_WARN,
         "gbd driver can't handle unmapped buffers");
      bioerror(bp, EIO);
      biodone(bp);
      return;
   }

   v_addr = bp->b_dmaaddr;

   /* Compute number of pages received.
   * The dma_len field provides the number of pages to
   * map. Note that this may be larger than the actual
   * number of bytes involved in the transfer. This is
   * because the transfer may cross page boundaries,
   * requiring an extra page to be mapped. Limit to
   * number of scatter/gather registers on board.
   * Note that this sample driver doesn't handle the
   * case of requests > than # of registers!
   * numpages() is a macro declared in sys/sysmacros.h
   */
   npages = numpages (v_addr, bp->b_dmalen);
   /*
   * Provide the beginning byte offset and count to the
   * device.
   */
   gbd_device[unit]->offset =
         (unsigned long)bp->b_dmaaddr & (NBPC-1);
   if(npages > GBD_NUM_DMA_PGS) {
      npages = GBD_NUM_DMA_PGS;
      cmn_err(CE_WARN,
      "request too large, only %d pages max", npages);
      if(gbd_device[unit]->offset)
         gbd_device[unit]->count = NBPC -
         gbd_device[unit]->offset + (npages-1)*NBPC;
      else
         gbd_device[unit]->count = npages*NBPC;
      bp->b_resid = bp->b_count - gbd_device[unit]->count;
   }
   else
      gbd_device[unit]->count = bp->b_count;

   /* Translate the virtual address of each page to a
   * physical page number and load it into the next
   * scatter/gather register. btop() 
   * converts the byte value to a page value after
   * rounding down the byte value to a full page.
   */
   for (i = 0; i < npages; i++) {
      *sgregisters++ = btop(kvtophys(v_addr));

      /*
      /* Get the next virtual address to translate.
      * (NBPC is a symbolic constant for the page
      * size in bytes)
      */

      v_addr += NBPC;
   }

   if ((bp->b_flags & B_READ) == 0)
      gbd_device[unit]->direction = GBD_WRITE;
   else
      gbd_device[unit]->direction = GBD_READ;
   gbd_device[unit]->command = GBD_GO;   /* start DMA */

   /* and return; upper layers of kernel wait for iodone(bp) */
}


Next | Prev | Up | Top | Contents | Index